#!/usr/bin/env python3
"""
修复版内网文件传输服务器 - 带真正的进度条
- 实时显示上传进度百分比
- 显示上传速度
- 显示剩余时间
- 支持多人上传下载
- 支持大文件

使用方法：python3 fixed_server.py
"""

import os
import sys
import socket
import json
import time
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import unquote, parse_qs, urlparse
from datetime import datetime
import tempfile

# ========== 配置 ==========
PORT = 3000
UPLOAD_DIR = "shared_files"
MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024  # 10GB
CHUNK_SIZE = 8192

# 创建上传目录
if not os.path.exists(UPLOAD_DIR):
    os.makedirs(UPLOAD_DIR)

# ========== 工具函数 ==========
def get_local_ip():
    """获取本机IP地址"""
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("8.8.8.8", 80))
        ip = s.getsockname()[0]
        s.close()
        return ip
    except:
        return "localhost"

def format_size(size):
    """格式化文件大小"""
    for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
        if size < 1024.0:
            return f"{size:.2f} {unit}"
        size /= 1024.0
    return f"{size:.2f} PB"

def format_time(timestamp):
    """格式化时间"""
    return datetime.fromtimestamp(timestamp).strftime('%m-%d %H:%M')

# ========== HTML页面（带真正的进度条） ==========
def get_html_page(ip, port):
    """生成HTML页面"""
    return f'''<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>局域网文件共享</title>
    <style>
        * {{
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }}
        
        body {{
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }}
        
        .container {{
            max-width: 900px;
            margin: 0 auto;
        }}
        
        .card {{
            background: white;
            border-radius: 20px;
            padding: 30px;
            margin-bottom: 20px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
        }}
        
        h1 {{
            color: #333;
            font-size: 32px;
            margin-bottom: 10px;
        }}
        
        .subtitle {{
            color: #666;
            font-size: 16px;
            margin-bottom: 20px;
        }}
        
        .info-box {{
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 20px;
            border-radius: 10px;
            margin-bottom: 20px;
        }}
        
        .info-item {{
            margin: 10px 0;
            font-size: 16px;
        }}
        
        .upload-form {{
            border: 3px dashed #667eea;
            border-radius: 15px;
            padding: 40px;
            text-align: center;
            background: rgba(102, 126, 234, 0.05);
        }}
        
        .upload-icon {{
            font-size: 64px;
            margin-bottom: 20px;
        }}
        
        .file-input {{
            margin: 20px 0;
        }}
        
        .btn {{
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            padding: 12px 30px;
            border-radius: 25px;
            cursor: pointer;
            font-size: 16px;
            margin: 10px;
        }}
        
        .btn:hover {{
            opacity: 0.9;
            transform: scale(1.05);
        }}
        
        .btn:disabled {{
            opacity: 0.5;
            cursor: not-allowed;
            transform: scale(1);
        }}
        
        /* 进度条样式 */
        .progress-container {{
            display: none;
            margin: 20px 0;
            text-align: left;
        }}
        
        .progress-container.active {{
            display: block;
        }}
        
        .progress-bar-bg {{
            background: #e0e0e0;
            border-radius: 10px;
            height: 30px;
            overflow: hidden;
            position: relative;
            margin: 10px 0;
        }}
        
        .progress-bar {{
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            height: 100%;
            width: 0%;
            border-radius: 10px;
            transition: width 0.3s ease;
            position: relative;
        }}
        
        .progress-text {{
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-weight: bold;
            font-size: 14px;
            text-shadow: 0 1px 2px rgba(0,0,0,0.3);
            z-index: 10;
            width: 100%;
            text-align: center;
        }}
        
        .progress-info {{
            display: flex;
            justify-content: space-between;
            color: #666;
            font-size: 14px;
            margin-top: 5px;
        }}
        
        .progress-filename {{
            font-weight: 600;
            color: #333;
            margin-bottom: 10px;
            font-size: 16px;
        }}
        
        .file-item {{
            background: #f8f9fa;
            border-radius: 12px;
            padding: 20px;
            margin-bottom: 10px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }}
        
        .file-name {{
            font-weight: 600;
            color: #333;
            font-size: 16px;
        }}
        
        .file-meta {{
            color: #666;
            font-size: 13px;
            margin-top: 5px;
        }}
        
        .file-actions {{
            display: flex;
            gap: 10px;
        }}
        
        .btn-small {{
            background: #667eea;
            color: white;
            border: none;
            padding: 8px 20px;
            border-radius: 20px;
            cursor: pointer;
            font-size: 14px;
            text-decoration: none;
            display: inline-block;
        }}
        
        .btn-small:hover {{
            background: #764ba2;
        }}
        
        .btn-delete {{
            background: #e74c3c;
        }}
        
        .btn-delete:hover {{
            background: #c0392b;
        }}
        
        .empty {{
            text-align: center;
            padding: 60px 20px;
            color: #999;
        }}
        
        .message {{
            padding: 15px;
            margin: 20px 0;
            border-radius: 10px;
            text-align: center;
        }}
        
        .success {{
            background: #d4edda;
            color: #155724;
        }}
        
        .error {{
            background: #f8d7da;
            color: #721c24;
        }}
    </style>
</head>
<body>
    <div class="container">
        <!-- 头部信息 -->
        <div class="card">
            <h1>📁 局域网文件共享中心</h1>
            <p class="subtitle">在同一网络内快速分享文件</p>
            
            <div class="info-box">
                <div class="info-item">
                    <strong>📡 局域网访问地址：</strong>
                    <span style="font-size: 20px;">http://{ip}:{port}</span>
                </div>
                <div class="info-item">
                    <strong>📂 共享文件夹：</strong> {UPLOAD_DIR}
                </div>
                <div class="info-item">
                    <strong>💾 最大文件大小：</strong> 10 GB
                </div>
            </div>
        </div>
        
        <!-- 上传表单 -->
        <div class="card">
            <h2 style="margin-bottom: 20px;">📤 上传文件</h2>
            <div class="upload-form">
                <div class="upload-icon">📁</div>
                <form method="POST" action="/upload" enctype="multipart/form-data" id="uploadForm">
                    <input type="file" name="file" class="file-input" id="fileInput" required>
                    <br><br>
                    <button type="submit" class="btn" id="uploadBtn">上传文件</button>
                </form>
                
                <!-- 进度条容器 -->
                <div id="progressContainer" class="progress-container">
                    <div class="progress-filename" id="progressFilename"></div>
                    <div class="progress-bar-bg">
                        <div class="progress-bar" id="progressBar">
                            <div class="progress-text" id="progressText">0%</div>
                        </div>
                    </div>
                    <div class="progress-info">
                        <span id="progressSpeed">速度: 0 KB/s</span>
                        <span id="progressSize">0 MB / 0 MB</span>
                        <span id="progressTime">剩余时间: 计算中...</span>
                    </div>
                </div>
                
                <div id="message"></div>
            </div>
        </div>
        
        <!-- 文件列表 -->
        <div class="card">
            <h2 style="margin-bottom: 20px;">📋 共享文件列表</h2>
            <div id="fileList">
                <div class="empty">
                    <div style="font-size: 64px; opacity: 0.5;">📭</div>
                    <p>加载中...</p>
                </div>
            </div>
        </div>
    </div>
    
    <script>
        // 全局变量
        var fileListElement = document.getElementById('fileList');
        var messageElement = document.getElementById('message');
        var uploadForm = document.getElementById('uploadForm');
        var fileInput = document.getElementById('fileInput');
        var uploadBtn = document.getElementById('uploadBtn');
        var progressContainer = document.getElementById('progressContainer');
        var progressBar = document.getElementById('progressBar');
        var progressText = document.getElementById('progressText');
        var progressFilename = document.getElementById('progressFilename');
        var progressSpeed = document.getElementById('progressSpeed');
        var progressSize = document.getElementById('progressSize');
        var progressTime = document.getElementById('progressTime');
        
        // 格式化文件大小
        function formatFileSize(bytes) {{
            if (bytes === 0) return '0 B';
            var k = 1024;
            var sizes = ['B', 'KB', 'MB', 'GB'];
            var i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }}
        
        // 格式化时间
        function formatTime(seconds) {{
            if (seconds === Infinity || isNaN(seconds)) return '计算中...';
            if (seconds < 60) return Math.round(seconds) + ' 秒';
            if (seconds < 3600) return Math.round(seconds / 60) + ' 分钟';
            return Math.round(seconds / 3600) + ' 小时';
        }}
        
        // 上传表单处理
        uploadForm.addEventListener('submit', function(e) {{
            e.preventDefault();
            
            var file = fileInput.files[0];
            if (!file) {{
                alert('请选择文件');
                return;
            }}
            
            var formData = new FormData();
            formData.append('file', file);
            
            // 准备进度显示
            progressContainer.classList.add('active');
            progressFilename.textContent = '正在上传: ' + file.name;
            progressBar.style.width = '0%';
            progressText.textContent = '0%';
            messageElement.innerHTML = '';
            uploadBtn.disabled = true;
            
            var xhr = new XMLHttpRequest();
            var startTime = Date.now();
            var lastLoaded = 0;
            
            // 监听上传进度
            xhr.upload.onprogress = function(e) {{
                if (e.lengthComputable) {{
                    // 计算进度百分比
                    var percentComplete = Math.round((e.loaded / e.total) * 100);
                    progressBar.style.width = percentComplete + '%';
                    progressText.textContent = percentComplete + '%';
                    
                    // 计算速度
                    var currentTime = Date.now();
                    var elapsedTime = (currentTime - startTime) / 1000; // 秒
                    var speed = e.loaded / elapsedTime; // bytes per second
                    progressSpeed.textContent = '速度: ' + formatFileSize(speed) + '/s';
                    
                    // 显示大小
                    progressSize.textContent = formatFileSize(e.loaded) + ' / ' + formatFileSize(e.total);
                    
                    // 计算剩余时间
                    var remaining = e.total - e.loaded;
                    var remainingTime = remaining / speed; // 秒
                    progressTime.textContent = '剩余时间: ' + formatTime(remainingTime);
                }}
            }};
            
            // 上传完成
            xhr.onload = function() {{
                uploadBtn.disabled = false;
                progressContainer.classList.remove('active');
                
                if (xhr.status === 200) {{
                    messageElement.innerHTML = '<div class="message success">✅ 上传成功！文件已保存。</div>';
                    fileInput.value = '';
                    loadFiles();
                    setTimeout(function() {{
                        messageElement.innerHTML = '';
                    }}, 3000);
                }} else {{
                    messageElement.innerHTML = '<div class="message error">❌ 上传失败！' + xhr.responseText + '</div>';
                }}
            }};
            
            // 上传错误
            xhr.onerror = function() {{
                uploadBtn.disabled = false;
                progressContainer.classList.remove('active');
                messageElement.innerHTML = '<div class="message error">❌ 网络错误！请检查连接。</div>';
            }};
            
            // 上传中断
            xhr.onabort = function() {{
                uploadBtn.disabled = false;
                progressContainer.classList.remove('active');
                messageElement.innerHTML = '<div class="message error">⚠️ 上传已取消</div>';
            }};
            
            xhr.open('POST', '/upload');
            xhr.send(formData);
        }});
        
        // 加载文件列表
        function loadFiles() {{
            var xhr = new XMLHttpRequest();
            xhr.onload = function() {{
                if (xhr.status === 200) {{
                    try {{
                        var files = JSON.parse(xhr.responseText);
                        updateFileList(files);
                    }} catch(e) {{
                        console.error('解析文件列表失败:', e);
                    }}
                }}
            }};
            xhr.open('GET', '/api/files');
            xhr.send();
        }}
        
        // 更新文件列表显示
        function updateFileList(files) {{
            if (files.length === 0) {{
                fileListElement.innerHTML = '<div class="empty"><div style="font-size: 64px; opacity: 0.5;">📭</div><p>暂无共享文件</p></div>';
                return;
            }}
            
            var html = '';
            for (var i = 0; i < files.length; i++) {{
                var file = files[i];
                html += '<div class="file-item">';
                html += '  <div>';
                html += '    <div class="file-name">' + escapeHtml(file.name) + '</div>';
                html += '    <div class="file-meta">' + file.size_formatted + ' · ' + file.time_formatted + '</div>';
                html += '  </div>';
                html += '  <div class="file-actions">';
                html += '    <a class="btn-small" href="/download/' + encodeURIComponent(file.name) + '">下载</a>';
                html += '    <button class="btn-small btn-delete" data-filename="' + escapeHtml(file.name) + '">删除</button>';
                html += '  </div>';
                html += '</div>';
            }}
            
            fileListElement.innerHTML = html;
            
            // 添加删除按钮事件
            var deleteButtons = document.querySelectorAll('.btn-delete');
            for (var i = 0; i < deleteButtons.length; i++) {{
                deleteButtons[i].addEventListener('click', handleDelete);
            }}
        }}
        
        // 处理删除
        function handleDelete(e) {{
            var filename = e.target.getAttribute('data-filename');
            if (!confirm('确定删除文件: ' + filename + ' ?')) {{
                return;
            }}
            
            var xhr = new XMLHttpRequest();
            xhr.onload = function() {{
                if (xhr.status === 200) {{
                    loadFiles();
                }} else {{
                    alert('删除失败');
                }}
            }};
            xhr.open('DELETE', '/api/delete/' + encodeURIComponent(filename));
            xhr.send();
        }}
        
        // HTML转义
        function escapeHtml(text) {{
            var div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }}
        
        // 初始加载
        loadFiles();
        
        // 每5秒刷新一次
        setInterval(loadFiles, 5000);
    </script>
</body>
</html>'''

# ========== HTTP处理器 ==========
class FileServerHandler(BaseHTTPRequestHandler):
    
    def do_GET(self):
        """处理GET请求"""
        parsed_path = urlparse(self.path)
        
        if parsed_path.path == '/':
            self.serve_home()
        elif parsed_path.path == '/api/files':
            self.list_files()
        elif parsed_path.path.startswith('/download/'):
            self.download_file()
        else:
            self.send_error(404)
    
    def do_POST(self):
        """处理POST请求"""
        if self.path == '/upload':
            self.handle_upload()
        else:
            self.send_error(404)
    
    def do_DELETE(self):
        """处理DELETE请求"""
        if self.path.startswith('/api/delete/'):
            self.delete_file()
        else:
            self.send_error(404)
    
    def serve_home(self):
        """返回主页"""
        html = get_html_page(get_local_ip(), PORT)
        self.send_response(200)
        self.send_header('Content-type', 'text/html; charset=utf-8')
        self.end_headers()
        self.wfile.write(html.encode('utf-8'))
    
    def list_files(self):
        """列出文件"""
        files = []
        try:
            for filename in os.listdir(UPLOAD_DIR):
                filepath = os.path.join(UPLOAD_DIR, filename)
                if os.path.isfile(filepath):
                    stat = os.stat(filepath)
                    files.append({
                        'name': filename,
                        'size': stat.st_size,
                        'size_formatted': format_size(stat.st_size),
                        'time': stat.st_mtime,
                        'time_formatted': format_time(stat.st_mtime)
                    })
        except Exception as e:
            print(f"列出文件错误: {e}")
        
        files.sort(key=lambda x: x['time'], reverse=True)
        
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        self.wfile.write(json.dumps(files).encode())
    
    def handle_upload(self):
        """处理上传"""
        try:
            content_length = int(self.headers.get('Content-Length', 0))
            
            if content_length > MAX_FILE_SIZE:
                self.send_error(413, "File too large")
                return
            
            # 读取数据到临时文件
            with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
                remaining = content_length
                while remaining > 0:
                    chunk_size = min(CHUNK_SIZE, remaining)
                    chunk = self.rfile.read(chunk_size)
                    if not chunk:
                        break
                    tmp_file.write(chunk)
                    remaining -= len(chunk)
                tmp_path = tmp_file.name
            
            # 解析multipart数据
            with open(tmp_path, 'rb') as f:
                data = f.read()
            
            # 提取boundary
            content_type = self.headers.get('Content-Type', '')
            if 'boundary=' not in content_type:
                os.unlink(tmp_path)
                self.send_error(400, "Invalid content type")
                return
                
            boundary = content_type.split('boundary=')[1]
            boundary_bytes = f'--{boundary}'.encode()
            
            # 查找文件
            parts = data.split(boundary_bytes)
            filename = None
            file_content = None
            
            for part in parts:
                if b'Content-Disposition: form-data' in part and b'filename=' in part:
                    header_end = part.find(b'\r\n\r\n')
                    if header_end != -1:
                        header = part[:header_end].decode('utf-8', errors='ignore')
                        content = part[header_end + 4:]
                        
                        # 移除尾部的boundary标记
                        if content.endswith(b'\r\n'):
                            content = content[:-2]
                        if content.endswith(b'--\r\n'):
                            content = content[:-4]
                        if content.endswith(b'--'):
                            content = content[:-2]
                        
                        # 提取文件名
                        if 'filename="' in header:
                            filename = header.split('filename="')[1].split('"')[0]
                            # 处理中文文件名
                            try:
                                filename = filename.encode('latin-1').decode('utf-8')
                            except:
                                pass
                        
                        file_content = content
                        break
            
            # 清理临时文件
            os.unlink(tmp_path)
            
            if filename and file_content:
                # 处理文件名冲突
                base_name, ext = os.path.splitext(filename)
                counter = 1
                save_path = os.path.join(UPLOAD_DIR, filename)
                while os.path.exists(save_path):
                    filename = f"{base_name}_{counter}{ext}"
                    save_path = os.path.join(UPLOAD_DIR, filename)
                    counter += 1
                
                # 保存文件
                with open(save_path, 'wb') as f:
                    f.write(file_content)
                
                print(f"✅ 上传成功: {filename} ({format_size(len(file_content))})")
                
                self.send_response(200)
                self.send_header('Content-type', 'text/plain')
                self.end_headers()
                self.wfile.write(b'OK')
            else:
                self.send_error(400, "No file found in request")
                
        except Exception as e:
            print(f"❌ 上传错误: {e}")
            self.send_error(500, str(e))
    
    def download_file(self):
        """下载文件"""
        filename = unquote(self.path[10:])
        filepath = os.path.join(UPLOAD_DIR, filename)
        
        if not os.path.exists(filepath):
            self.send_error(404, "File not found")
            return
        
        try:
            file_size = os.path.getsize(filepath)
            
            self.send_response(200)
            self.send_header('Content-Type', 'application/octet-stream')
            self.send_header('Content-Disposition', f'attachment; filename="{filename}"')
            self.send_header('Content-Length', str(file_size))
            self.end_headers()
            
            with open(filepath, 'rb') as f:
                while True:
                    chunk = f.read(CHUNK_SIZE)
                    if not chunk:
                        break
                    self.wfile.write(chunk)
            
            print(f"📥 下载: {filename}")
        except Exception as e:
            print(f"下载错误: {e}")
            self.send_error(500, str(e))
    
    def delete_file(self):
        """删除文件"""
        filename = unquote(self.path[12:])
        filepath = os.path.join(UPLOAD_DIR, filename)
        
        if not os.path.exists(filepath):
            self.send_error(404, "File not found")
            return
        
        try:
            os.remove(filepath)
            print(f"🗑️ 删除: {filename}")
            
            self.send_response(200)
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
            self.wfile.write(b'OK')
        except Exception as e:
            self.send_error(500, str(e))
    
    def log_message(self, format, *args):
        """禁用默认日志"""
        return

# ========== 主程序 ==========
def main():
    local_ip = get_local_ip()
    
    print('\n' + '=' * 60)
    print('🚀 局域网文件共享服务器（真正带进度显示版）')
    print('=' * 60)
    print(f'📡 本地访问: http://localhost:{PORT}')
    print(f'📱 局域网访问: http://{local_ip}:{PORT}')
    print('=' * 60)
    print('✨ 功能特性:')
    print('   ✅ 实时显示上传进度百分比')
    print('   ✅ 显示上传速度')
    print('   ✅ 显示剩余时间')
    print('   ✅ 显示已上传/总大小')
    print('   • 支持多人同时上传下载')
    print('   • 漂亮的进度条界面')
    print('   • 支持大文件（10GB）')
    print('   • 自动刷新文件列表')
    print('=' * 60)
    print(f'📁 文件位置: {os.path.abspath(UPLOAD_DIR)}')
    print('=' * 60)
    print('\n🔗 把上面的局域网地址分享给其他人')
    print('   他们用浏览器打开就能上传下载文件')
    print('\n📌 按 Ctrl+C 停止服务器\n')
    
    try:
        server = HTTPServer(('0.0.0.0', PORT), FileServerHandler)
        server.serve_forever()
    except KeyboardInterrupt:
        print('\n\n👋 服务器已停止')
        server.shutdown()
    except Exception as e:
        print(f'\n❌ 服务器启动失败: {e}')
        print('   可能端口被占用，请尝试修改PORT变量')

if __name__ == '__main__':
    main()
